home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
oper_sys
/
presto
/
prest1_0.lha
/
src
/
preempt.C
< prev
next >
Wrap
C/C++ Source or Header
|
1991-12-11
|
18KB
|
767 lines
/*
* preempt.c
*
*
* Preemption interrupt routines.
*
* Preemption works by having a single designated process(or) take a VTALRM
* every Scheduler::sc_quantum milliseconds. The designated process
* takes a snapshot of the readyq, estimating how many ready threads
* there are. For each processor, if that processor is running a
* preemptable thread (scheduler threads are non-preemptable), and
* the thread has been running for longer than the quantum, the processor
* on which the thread is running is handed a a SIGUSR1, to force
* a resched.
*
* In the resched interrupt handler (or from the VTALRM handler if
* the designated alrm handler is running on a processor which needs
* to be preempted), we diddle with the return sigcontext so that on
* return from the signal handler, we will be running a preface to the
* actual thread switch routine, as though it were called directly
* by the user thread. This makes context switching always appear
* synchronous from the standpoint of the scheduler.
*
* The signal handling code here is (unfortunately) machine dependent and
* will need to be rewritten somewhat to run under 4.3 or the vax.
*
* Note that the MIPS port already uses machine independent methods
* of restoring context during preemption. This method only relies on
* the 4.3BSD sigcontext entry points (including the undocumented sigreturn()).
*
* This code needs to be made part of a general sigobject that interfaces
* PRESTO code to asynchronous events.
*/
#define _PREEMPT_C
#include <stddef.h>
#include <sys/types.h>
#include <signal.h>
#include <osfcn.h>
#include "presto.h"
//
// On the sequent, each process has a private interrupts_enabled
// variable. On other machines, interrupts_enabled is implemented
// as a field in each process object. If interrupts are disabled,
// then thisthread is, by definition, not preemptable. A processor
// could be running with interrupts enabled, but thisthread could
// not be allowing preemption. See functions for enabling and
// disabling interrupts at the bottom of this file.
//
#ifdef sequent
private_t int interrupts_enabled = 1;
#endif /* sequent */
#ifdef sun
# ifndef THREAD_HAS_INTERRUPTIBLE_FIELD
private_t int interrupts_enabled = 1;
# endif
#endif
int disable_interrupts();
void enable_interrupts();
int interrupts_are_enabled();
int preemption_enabled = 0; // preemption ticker is running
#ifdef PREEMPT
static private_t int osigmask;
void sigpreempt_init ();
sigvec_handler_t sigpreempt_notify (int sig, int code, struct sigcontext *scp);
sigvec_handler_t sigpreempt_alrm (int sig, int code, struct sigcontext *scp);
void sigpreempt_unblock ();
//
// signal reentry point
//
shared_t int numalarms = 0; // damn the concurrency
shared_t int numnotifies = 0;
shared_t int numfalsehits = 0;
#define SIGSTACKSIZ 4096
#define SIGPREEMPT_NOTIFY SIGUSR1
#define SIGPREEMPT_ALRM SIGVTALRM
//
// Take the two preempt signals on the signal stack
//
static struct sigvec svec_notify = {
sigpreempt_notify, sigmask(SIGPREEMPT_NOTIFY), 1};
static struct sigvec svec_alrm ={
sigpreempt_alrm, sigmask(SIGPREEMPT_ALRM), 1};
static private_t int sstack[SIGSTACKSIZ];
static struct sigstack signalstack = { (caddr_t)&sstack[SIGSTACKSIZ-1], 0 };
// force kernel sigvec to be called
#ifdef sequent
// #define SIGVEC_OS _sigvec
#define SIGVEC_OS sigvec
#else
#define SIGVEC_OS sigvec
#endif
extern int SIGVEC_OS(int, const struct sigvec*, struct sigvec*);
void
sigpreempt_init()
{
struct sigstack oss;
// Check if we are already running on an alternate stack
if (sigstack((struct sigstack*)0, &oss) < 0) {
perror("sigstack1");
fatalerror();
}
if (oss.ss_sp == 0) { // must set our own
if (sigstack(&signalstack, (struct sigstack*)0) < 0) {
perror("sigstack2");
fatalerror();
}
}
if (SIGVEC_OS(SIGPREEMPT_ALRM, &svec_alrm, 0) < 0) {
perror("sigvecusr1");
fatalerror();
}
if (SIGVEC_OS(SIGPREEMPT_NOTIFY, &svec_notify, 0) < 0) {
perror("sigvecvtalrm");
fatalerror();
}
osigmask = sigblock(0); // get original signal mask
return;
}
void
sigpreempt_stopclock()
{
struct itimerval it;
disable_interrupts();
preemption_enabled = 0;
sigblock(sigmask(SIGPREEMPT_NOTIFY)|sigmask(SIGSEGV));
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
setitimer(ITIMER_VIRTUAL, &it, (struct itimerval*)0);
}
//
// this should only run in the parent master scheduler
//
void
sigpreempt_beginclock(struct timeval *quantum)
{
struct itimerval it;
preemption_enabled = 1;
if (quantum->tv_sec == 0 && quantum->tv_usec < 100000)
quantum->tv_usec = 100000; // 100ms minimum quantum
it.it_interval = *quantum;
it.it_value = *quantum;
if (setitimer(ITIMER_VIRTUAL, &it, (struct itimerval*)0) < 0) {
perror("setitimer");
fatalerror();
}
sigpreempt_unblock();
enable_interrupts();
}
void
sigpreempt_unblock()
{
sigsetmask(osigmask);
}
//
// handler for taking care of preemption when signaled. We can be called
// synchronously: if the vtalrm handler process needs to be preempted
// when done handling the alarm
//
// asynchronously:
// if the process on which we are running needs to get
// preempted as decided by the vtalrm handler.
//
// We block the offending signal for after we return until after
// the main preemption code has done its thing.
sigvec_handler_t
sigpreempt_notify( int sig, int code, struct sigcontext *scp)
{
#ifdef mips
extern char atomic_test_and_set[];
extern char atomic_test_and_set_rcs[];
struct sigcontext *newscp;
void mips_preempt_reentry(struct sigcontext *, int);
sig = sig; // satisfy cfront
code = code; // satisfy cfront
#else
int *sp = (int*)scp->sc_sp;
#endif /* mips */
/* check that we can preempt
*/
if (!interrupts_are_enabled()) {
code=code; // make compiler happy
#ifdef mips
return;
#else
return 0;
#endif
}
#ifdef ns32000
sp[0] = scp->sc_modpsr;
#endif
#ifdef i386
sp[0] = scp->sc_flags;
#endif
#ifdef sequent
sp[1] = scp->sc_pc;
scp->sc_sp -= 2 * sizeof(int);
#endif /* sequent */
#ifdef vax
//
// What we *really* want to do is push the interrupted pc on the
// stack and fake a jsb to preempt_reentry, but Ultrix won't
// let us change the stack pointer (sigh). Instead we save
// the interrupted pc in the thread object, set our sigcontext
// pc to preempt_reentry and fix things up to look like an
// interrupt occurred once we get there.
//
thisthread->setpc(scp->sc_pc);
#endif /* vax */
#ifdef sun
thisthread->setpc(scp->sc_pc);
#endif /* sun */
#ifdef mips
// This will ensure that once preemption is complete, and we
// attempt to get back to the stack of the "interrupted"
// thread, we will in fact begin a test and set operation,
// thus assuring its "atomicity" (after original by Raj)
if (scp->sc_pc >= (int) atomic_test_and_set &&
scp->sc_pc < (int) atomic_test_and_set_rcs)
scp->sc_pc = (int)atomic_test_and_set;
// copy the return context into an area immediately above
// the stack of the interrupted thread. Note that it is not
// "not exactly the world's most bullet-proof assumption"
// that such space will exist, but it seems to work fine.
newscp = (struct sigcontext *) (scp->sc_regs[29] - sizeof (struct sigcontext));
bcopy (scp, newscp, sizeof (struct sigcontext));
// set up the return context so that we can call
// preempt_preempt(), and then pop back to the original
// preempted thread (when its get run again).
scp->sc_regs[4] = (int)newscp;
scp->sc_regs[5] = (int)newscp; // bogus, but I'm not sure if a
// 2nd argument affects the
// reset of the stack pointer
// update the stack pointer
scp->sc_regs[29] = (int)newscp - (2 * sizeof (int));
// this is the function that will call preempt_preempt(),
// and then sigreturn() back to the context stored at newscp.
scp->sc_pc = (int)mips_preempt_reentry;
#else
scp->sc_pc = (int)preempt_reentry;
#endif /* mips */
//
// block the incoming signal until we are through preempting
//
osigmask = scp->sc_mask;
scp->sc_mask |= sigmask(sig);
numnotifies++;
#ifdef mips
return;
#else
return 0;
#endif
}
//
//
// VTALRM handler for process designated to control preemption.
// We are a friend of the scheduler
//
sigvec_handler_t
sigpreempt_alrm( int sig, int code, struct sigcontext *scp )
{
Process *p;
Thread *t;
int numtopreempt;
int i;
numtopreempt = sched->readyqlen();
// for fairness, we should cycle through modulo the number
// of processors, not always start at the beginning.
//
numalarms++;
double q = ((double)sched->quantum()) / 1000.0;
for (i = 0; numtopreempt && i < sched->sc_p_activeschedulers; i++) {
p = sched->sc_p_procs[i];
// slight race condition... we could get the running
// thread just as it is returning to the scheduler. We
// only can consider a NOTIFY interrupt as being
// a hint that we should really preempt.
t = p->runningthread();
if (t && t->canpreempt()) {
// DESIGNATED PROC MUST BE PREEMPTED
// Save ourselves from having to handle yet another
// signal.
if (p == thisproc) {
(void)sigpreempt_notify(sig, code, scp);
} else {
// just signal the other proc
kill(p->pid(), SIGPREEMPT_NOTIFY);
}
numtopreempt--;
}
}
#ifdef mips
return;
#else
return 0;
#endif
}
//
//
// Signal handler reentry point. From the standpoint of the system,
// it looks as though the currently executing thread falls into this
// routine.
//
// preempt_reentry_() is a never-called label.
//
#ifndef mips
#ifdef vax
int _hack;
#endif /* vax */
#ifdef sun
int _hack;
#endif /* sun */
void
preempt_reentry_()
{
void preempt_preempt();
asm(" .globl _preempt_reentry");
asm("_preempt_reentry:");
// must be sure to run off own frame in preempt_preempt. At this
// point, we are on the frame of the interrupted routine.
#ifdef vax
//
// The following hack is compiler-dependent and dangerous.
// We are assuming that getpc uses no registers besides r0.
// Also, this won't work on a multiprocessor because we are
// using a global (_hack) as a temporary for the interrupted
// pc. This is a "temporary" uniprocessor vax hack. The code
// should be changed to use a faked jsb/rsb when the Ultrix
// bug alluded to in sigpreempt_notify is fixed.
//
// Here's the plan:
// 1. Push the psl and r0 on the stack.
// 2. Get the interrupted pc in _hack, push it on the stack.
// 3. Call preempt_preempt().
// 4. Sleazily restore r0.
// 5. Pop the pushed pc, put it where r0 was on the stack.
// 6. Restore the pc and psl with an REI.
//
asm("movpsl -(sp)");
asm("pushl r0");
_hack = thisthread->getpc();
asm("pushl __hack");
#endif /* vax */
#ifdef sun
//
// We are doing a similar thing on the sun, only since I don't
// know what to do with the status register (can't find the
// *!$! mnemonic), I just ignore it.
//
asm("movl a0, sp@-");
_hack = thisthread->getpc();
asm("movl __hack, sp@-");
#endif /* sun */
preempt_preempt();
// simulate a rti
#ifdef ns32000
asm("lprw upsr,2(sp)");
asm("adjspb -4");
asm("ret 0");
#endif
#ifdef i386
asm("popfl");
asm("ret");
#endif
#ifdef sun
//
// Stack should look like: sp-> PC
// a0
//
// We are doing this: PC
// sp-> PC
//
asm("movl sp@(4), a0");
asm("movl sp@, sp@(4)");
asm("addql #4, sp ");
asm("rts");
#endif /* sun */
//
// Vax stack looks like: sp-> PC
// r0
// PSL
//
// What we are doing is this:
// PC
// sp-> PC
// PSL
//
#ifdef vax
asm("movl 4(sp), r0");
asm("movl (sp), 4(sp)");
asm("addl2 $4, sp");
asm("rei");
#endif
}
#else /* mips */
// This is a wierd hack. We call the actual preemption
// routine having already returned from the signal handler.
// However, we saved the context that existed when we were
// were interrupted, in a sigcontext block stored at scp.
// When we're done preempting (presumably back in this
// thread again), we call the well-hidden kernel entry
// point sigreturn() (this is NOT documented anywhere) to
// restore that context for us, so that we appear to just
// carry on where we left off when the signal came in.
// Note that carrying on may mean restarting a test and set
// operation, since the MIPS has no atomic instructions.
void
mips_preempt_reentry (struct sigcontext *scp, int foo)
{
void preempt_preempt();
foo=foo; // satisfy cfront
preempt_preempt ();
sigreturn (scp);
}
#endif
//
// preempt_preempt()
//
// we are running as though we were called synchronously
// by the currently executing thread. Place the current
// thread on the ready queue and swtch back to the scheduler
// thread. For a tiny instant, the thread we place on the
// readyqueue will remain locked until the scheduler thread
// unlocks it. This will only be a problem if some other
// processor grabs it off the ready queue too soon. But,
// the fact that we are preempting means that the readyqueue
// is long, so it is not likely that this will happen. Even if
// it does, it only means that the scheduler thread which pulls
// it off will briefly spin.
//
void
preempt_preempt()
{
int checkpreempt_request();
//
// save scratch registers
//
#ifdef mc68020
{
asm("movl a0, sp@- ");
asm("movl a1, sp@- ");
asm("movl a2, sp@- ");
}
#endif /* mc68020 */
#ifdef ns32000
{
asm("movd r0, tos");
asm("movd r1, tos");
asm("movd r2, tos");
}
#endif
#ifdef i386
{
asm("pushl %eax");
asm("pushl %ecx");
asm("pushl %edx");
}
#endif
#ifdef vax
asm("movl r0, -(sp)");
asm("movl r1, -(sp)");
asm("movl r2, -(sp)");
#endif
#ifdef mips
// The mips swtch code does the register save for us
#endif /* mips */
//
// signal callout mechanism belongs here
//
if (checkpreempt_request()) {
sched->resume(thisthread);
sigpreempt_unblock();
if (thisthread->inspinlock())
error("Preempted thread holds a lock!");
thisthread->swtch();
} else {
numfalsehits++;
sigpreempt_unblock();
}
#ifdef mc68020
{
asm("movl sp@+, a2 ");
asm("movl sp@+, a1 ");
asm("movl sp@+, a0 ");
}
#endif /* mc68020 */
#ifdef ns32000
{
// replace scratch registers
asm("movd tos, r2");
asm("movd tos, r1");
asm("movd tos, r0");
}
#endif
#ifdef i386
{
asm("popl %edx");
asm("popl %ecx");
asm("popl %eax");
}
#endif
#ifdef vax
asm("movl (sp)+, r2");
asm("movl (sp)+, r1");
asm("movl (sp)+, r0");
#endif /* vax */
}
//
// Verify that the currently executing thread really wants to be
// preempted.
//
static int
checkpreempt_request()
{
if (thisthread->canpreempt())
return 1;
#ifndef PREEMPT_DEBUG
else
return 0;
#else
//
// try to get a little more specific about why it can't be
//
if ( !thisthread->ispreemptable()) {
cerr << thisthread->flags()
<< "Bad Preempt: Thread is not preemptaable\n";
return 0;
} else if (thisthread->flags()&TF_SCHEDULER) {
cerr << "Bad Preempt: scheduler is running\n";
return 0;
}
#endif
}
#endif /* PREEMPT */
//
// These functions allow presto kernel code to mark process objects as
// non-interruptible. This is used by the memory allocation code when
// acquiring and releasing the malloc spinlock, and is also used to
// disable preemption ticks during a context switch. Note that the
// process is not interruptible even when it is spinning waiting for
// the malloc lock. This is also a problem for Spinlock objects. XXX
//
// On the sequent a private type is available, and using it for
// interrupts_enabled saves a few instructions on context switches.
// On other machines, each process object has a p_interruptible
// field and public member functions for enabling and disabling
// interrupts on that process object.
//
#ifdef sun
# ifdef THREAD_HAS_INTERRUPTIBLE_FIELD
int interrupts_are_enabled() {
if (preemption_enabled) {
return thisproc->interruptible();
} else
return 0;
}
int disable_interrupts() {
if (preemption_enabled) {
return thisproc->disable_interrupts();
} else
return 0;
}
void enable_interrupts() {
if (preemption_enabled)
thisproc->enable_interrupts();
}
# else
int interrupts_are_enabled() { return interrupts_enabled; }
int disable_interrupts() {
if (interrupts_enabled) {
interrupts_enabled = 0;
return 1;
} else
return 0;
}
void enable_interrupts() { interrupts_enabled = 1; }
# endif THREAD_HAS_INTERRUPTIBLE_FIELD
#endif /* sun */
#ifdef sequent
int
interrupts_are_enabled()
{
return interrupts_enabled;
}
extern int
disable_interrupts()
{
if (interrupts_enabled) {
interrupts_enabled = 0;
return 1;
} else
return 0;
}
extern void
enable_interrupts()
{
interrupts_enabled = 1;
}
#endif /* sequent */
#ifdef vax
int
interrupts_are_enabled()
{
if (preemption_enabled) {
return thisproc->interruptible();
} else
return 0;
}
extern int
disable_interrupts()
{
if (preemption_enabled) {
return thisproc->disable_interrupts();
} else
return 0;
}
extern void
enable_interrupts()
{
if (preemption_enabled)
thisproc->enable_interrupts();
}
#endif /* vax */
#ifdef mips
int
interrupts_are_enabled()
{
if (preemption_enabled) {
return thisproc->interruptible();
} else
return 0;
}
extern int
disable_interrupts()
{
if (preemption_enabled) {
return thisproc->disable_interrupts();
} else
return 0;
}
extern void
enable_interrupts()
{
if (preemption_enabled)
thisproc->enable_interrupts();
}
#endif /* mips */